﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SuperSerialization.Tree;
using System.Reflection;

namespace SuperSerialization
{
    /// <summary>
    /// Attempts to convert an in-memory object into a document node tree.
    /// </summary>
    class ObjectConverter
    {
        /// <summary>
        /// Attempts to convert the given object to a node tree.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <returns>The base node of the tree if successful.</returns>
        public static Node Convert(Object obj)
        {
            Node[] children = new Node[] { ConvertElement(obj) };
            BaseNode node = new BaseNode(children);
            return node;
        }

        /// <summary>
        /// Attempts to convert some kind of value element to a node.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <returns>A node.</returns>
        protected static Node ConvertElement(Object obj)
        {
            if(obj == null) return new LiteralNode();
            Type type = obj.GetType();
            if (type.IsArray)
            {
                return ConvertArray(obj as Array);
            }
            else if (type == typeof(string))
            {
                return new StringNode((string)obj);
            }
            else if (type.IsPrimitive)
            {
                return new LiteralNode(obj);
            }
            else
            {
                return ConvertObject(obj);
            }
        }

        /// <summary>
        /// Attempts to convert an object into a node.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <returns>The object node.</returns>
        protected static Node ConvertObject(Object obj)
        {
            Type type = obj.GetType();
            ObjectNode node = new ObjectNode();
            node.Type = type.ToString();
            Object defaultObject = type.Assembly.CreateInstance(type.FullName); // a default object for checking default values
            MemberInfo[] memberInfoArray = type.GetFields();
            foreach (MemberInfo info in memberInfoArray)
            {
                if (info.MemberType == MemberTypes.Field)
                {
                    // Skip if it has the [Skip] attribute
                    if (info.GetCustomAttributes(typeof(SuperSerial.Attributes.SkipAttribute), false).Length == 0)
                    {
                        FieldNode member = new FieldNode();
                        member.Type = ((FieldInfo)info).FieldType.ToString();
                        member.Name = info.Name;

                        object v = ((FieldInfo)info).GetValue(obj);
                        member.Value = ConvertElement(v);
                        // checking if default value is used (only works for literals)
                        if (!(member.Value is LiteralNode) || ((LiteralNode)member.Value).Value == null || !((LiteralNode)member.Value).Value.Equals(((FieldInfo)info).GetValue(defaultObject)))
                            node.Members.Add(member);
                    }
                }
            }
            return node;
        }

        /// <summary>
        /// Attempts to convert an in-memory array to an array node.
        /// </summary>
        /// <param name="array">The array to convert.</param>
        /// <returns>An array node.</returns>
        protected static ArrayNode ConvertArray(Array array)
        {
            Node[] nodeArray = new Node[array.Length];
            for (int i = 0; i < array.Length; i++)
            {
                nodeArray[i] = ConvertElement(array.GetValue(i));
            }
            return new ArrayNode(nodeArray);
        }
    }
}
